#include "adc.h"

// 存放adc采样结果的数组, 其中adcVal[0]存放VR的采样值, adcVal[1]存放Temp的采样值
// DMA负责将寄存器的值搬运到此处, 主程序可以使用这些值
uint16_t adcVal[2] = {0};

void myADC_Init(void)
{
    // ADC时钟最大不能超过14Mhz, 因此先配置其分频期
    // 将PCLK2分频器配置为/6, 即12Mhz
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);

    // RCC配置, 开启GPIOC和ADC1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    // 配置VR对应的PC1引脚GPIO
    // 此处结构体使用的是C99语法的`指定初始化`写法, 更简洁
    GPIO_InitTypeDef GPIO_initStruct =
        {
            .GPIO_Pin = MYADC_VR_Pin,
            // 模拟输入模式
            .GPIO_Mode = GPIO_Mode_AIN,
        };
    GPIO_Init(MYADC_VR_Port, &GPIO_initStruct);

    // 配置ADC通道, 注意开启的通道数(NbrOfChannel)为2
    // 1. ADC外设本身的初始化init配置
    ADC_InitTypeDef ADC_initStruct =
        {
            // 独立模式, 采样独立进行(多个ADC外设协同工作才使用其他模式)
            .ADC_Mode = ADC_Mode_Independent,
            // 是否开启扫描模式, 多通道扫描就是顺序地轮流采样转换
            // 此处有多个通道开启, 因此使用扫描模式
            .ADC_ScanConvMode = ENABLE,
            // 是否连续采样, 即采样完一路两个通道, 再次采样
            .ADC_ContinuousConvMode = ENABLE,
            // 是否由外部触发采样, 即由其他如定时器等外设事件触发
            // 此处不使用, 而是手动用命令(软件)启动
            .ADC_ExternalTrigConv = ADC_ExternalTrigConv_None,
            // 数据对齐方式
            .ADC_DataAlign = ADC_DataAlign_Right,
            // 开启通道数, 此处准备将通道11和16都使用, 因此为2个通道
            .ADC_NbrOfChannel = 2,
        };
    ADC_Init(ADC1, &ADC_initStruct);

    //-----------开启温度传感器的使能-----------
    ADC_TempSensorVrefintCmd(ENABLE);

    // 2. 配置通道
    // 参数3为rank值, 即通道优先级, 决定了一轮扫描采样中的采样顺序
    //      此处先采样VR值, 再采样Temp值
    // 参数4为采样花费使用的时钟周期数, 决定了采样一次花费的时间
    //      对于VR,是普通的外部引脚, 一般设置为中等值即可, 此处使用55.5个时钟周期
    //      此时采样一次花费时间为  (55.5 + 12.5) / 12Mhz 约等于 5.67us
    //      对于Temp, 手册指明采样时间最好>=17.1us, 因此需要使用239.5个时钟周期
    //      此时采样一次花费时间为 (239.5 + 12.5) / 12MHz 约等于21us
    ADC_RegularChannelConfig(ADC1, MYADC_VR_Channel, 1, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, MYADC_TEMP_Channel, 2, ADC_SampleTime_239Cycles5);

    //---------------中断配置--------------------
    // 此处不在中断中读取寄存器值, 而是用DMA直接传送, 因此不使用中断
    // ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
    // NVIC_InitTypeDef nvic_initStruct = {0};
    // nvic_initStruct.NVIC_IRQChannel = ADC1_2_IRQn;
    // nvic_initStruct.NVIC_IRQChannelPreemptionPriority = 2;
    // nvic_initStruct.NVIC_IRQChannelSubPriority = 2;
    // nvic_initStruct.NVIC_IRQChannelCmd = ENABLE;
    // NVIC_Init(&nvic_initStruct);

    // ----------DMA配置---------------
    DMA_InitTypeDef DMA_initStruct =
        {
            // 搬运源地址
            .DMA_PeripheralBaseAddr = (uint32_t)(&ADC1->DR),
            // 源地址总是DR寄存器, 地址不递增
            .DMA_PeripheralInc = DMA_PeripheralInc_Disable,
            // 采样值为12bit, 存放的寄存器为16bit, 因此每次搬运半字
            .DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord,
            
            // 搬运目标地址
            .DMA_MemoryBaseAddr = (uint32_t)adcVal,
            // 目标地址会递增, 第一个搬运到adcVal[0], 第二个搬运到adcVal[1]
            .DMA_MemoryInc = DMA_MemoryInc_Enable,
            // 存放的位置也是每次16bit的数据
            .DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord,
            
            // 非内存-内存模式, 而是内存-寄存器模式
            .DMA_M2M = DMA_M2M_Disable,
            // 方向为外设作为源地址, 即从ADC结果寄存器搬运到内存中
            .DMA_DIR = DMA_DIR_PeripheralSRC,
            // 传输个数, 注意这个值是"传输次数"而不是"总字节数"
            .DMA_BufferSize = 2,
            // 循环搬运
            .DMA_Mode = DMA_Mode_Circular,
            // 优先级
            .DMA_Priority = DMA_Priority_Medium,
        };
    DMA_Init(MYADC_DMA_Channel, &DMA_initStruct);

    // 使能部分
    // 先使能DMA
    DMA_Cmd(MYADC_DMA_Channel, ENABLE);
    // 再开启ADC的DMA功能
    ADC_DMACmd(ADC1, ENABLE);
    // 最后开启ADC本身
    ADC_Cmd(ADC1, ENABLE);

    //----------------校准---------------
    // 对于ADC, 为了读取数据的准确性, 应该在配置完之后, 第一次采样之前
    // 进行校准工作
    // 1. 重置校准器
    ADC_ResetCalibration(ADC1);
    // 2. 等待重置完成
    while (RESET != ADC_GetResetCalibrationStatus(ADC1))
    {
    }
    // 3. 开始校准
    ADC_StartCalibration(ADC1);
    // 4. 等待校准完成
    while (RESET != ADC_GetCalibrationStatus(ADC1))
    {
    }

    // 到此, 配置完成, 只要调用软件启动命令, ADC即开始采样

    // 在每一轮扫描式采样中:
    // 1. ADC先采样通道11, 获取VR采样值存入到DR寄存器, DMA立刻将这个值搬运到adcVal[0]处
    // 2. ADC再采样通道16, 获取Temp采样值存入DR寄存器, DMA立刻将这个值搬运到adcVal[1]处
    // 3. 此时一轮扫描采样和一轮DMA搬运都完成
    // 由于ADC和DMA都设置为了循环模式, 每一轮ADC扫描式采样完成, 又会重新进行一次
    // 同样DMA搬运完一轮, 又会重新开始搬运
}
